5.24. Типы данных
Типы данных
Переменные: именованные контейнеры для значений
Переменная в Julia — это именованная ссылка на значение, хранящееся в памяти. Имя переменной служит удобным способом обращения к данным без необходимости указывать их конкретное расположение в оперативной памяти. При создании переменной ей присваивается значение, и одновременно с этим значение получает определённый тип. В Julia каждое значение имеет тип, даже если программист явно его не указывает. Это означает, что переменная сама по себе не обладает фиксированным типом, но всегда связана с типом своего текущего значения.
Имена переменных в Julia могут содержать буквы, цифры, символы подчёркивания и даже Unicode-символы, включая греческие буквы и математические знаки. Такая гибкость позволяет использовать обозначения, близкие к научной нотации: например, допустимы имена вроде α, β₁, velocity, user_name. Однако имя переменной не может начинаться с цифры. Julia чувствителен к регистру, поэтому Value и value — это две разные переменные.
Присваивание значения переменной осуществляется с помощью оператора =. Например:
x = 42
name = "Julia"
π_approx = 3.14159
В этих примерах переменные x, name и π_approx автоматически получают типы Int64, String и Float64 соответственно (на большинстве современных 64-битных систем). Julia выводит тип значения на основе литерала или результата выражения, и этот процесс происходит во время выполнения, но с учётом информации, доступной компилятору на этапе анализа кода.
Переменные в Julia могут быть переприсвоены новыми значениями любого типа. Это свойство делает язык динамически типизированным на уровне синтаксиса. Однако внутри функций, особенно при использовании аннотаций типов, компилятор может проводить агрессивную оптимизацию, предполагая, что тип переменной остаётся неизменным, что значительно повышает производительность.
Типы данных: категории значений
Тип в Julia — это не просто метка, а полноценный объект первого класса. Каждый тип описывает множество значений и операции, которые можно над ними выполнять. Все типы в Julia образуют древовидную иерархию, корнем которой является абстрактный тип Any. От него наследуются все остальные типы, включая как встроенные, так и пользовательские.
Типы в Julia делятся на два основных класса: абстрактные и конкретные.
Абстрактные типы не могут иметь экземпляров. Они служат для организации иерархии и определения общих свойств групп типов. Например, Number — это абстрактный тип, объединяющий все числовые типы, такие как целые (Integer) и вещественные (AbstractFloat). Абстрактный тип Integer, в свою очередь, включает в себя конкретные типы вроде Int8, Int16, Int32, Int64, Int128 и беззнаковые аналоги UInt8, UInt16 и так далее.
Конкретные типы могут иметь экземпляры — то есть реальные значения, которые можно хранить в переменных и передавать в функции. Примеры конкретных типов: Int64, Float64, Bool, Char, String, а также составные типы, определяемые пользователем, такие как struct.
Важной особенностью системы типов Julia является то, что она параметрическая. Это означает, что многие типы принимают один или несколько параметров, уточняющих их поведение или структуру. Наиболее известный пример — массивы. Тип Array{T, N} описывает N-мерный массив, элементы которого имеют тип T. Например, Array{Int64, 1} — это одномерный массив целых 64-битных чисел, а Array{Float64, 2} — двумерный массив вещественных чисел. Параметризация позволяет писать обобщённый код, который работает с любыми подходящими типами, сохраняя при этом высокую производительность.
Числовые типы
Julia предоставляет богатый набор встроенных числовых типов, соответствующих стандартным аппаратным представлениям. Целочисленные типы включают как знаковые (Int8, Int16, Int32, Int64, Int128), так и беззнаковые (UInt8, UInt16, UInt32, UInt64, UInt128). Выбор конкретного типа зависит от требований к диапазону значений и потреблению памяти. По умолчанию целочисленные литералы, такие как 42, интерпретируются как Int, который на 64-битных системах совпадает с Int64.
Вещественные числа представлены типами с плавающей запятой: Float16, Float32, Float64. Тип Float64 используется по умолчанию для литералов вроде 3.14. Julia также поддерживает рациональные числа (Rational{T}) и комплексные числа (Complex{T}), которые строятся поверх базовых числовых типов. Например, 1//2 создаёт рациональное число, а 1 + 2im — комплексное.
Числовые типы в Julia строго разделены: целое число 1 и вещественное 1.0 имеют разные типы и не считаются равными в строгом смысле (===), хотя их значения могут быть эквивалентны (==). Это позволяет избегать неявных преобразований, которые могут привести к потере точности или неожиданному поведению.
Булевы и символьные типы
Тип Bool представляет логические значения и имеет только два возможных экземпляра: true и false. Этот тип используется в условных выражениях, циклах и логических операциях. Julia не приводит другие типы к булевым автоматически: попытка использовать, например, число в условии вызовет ошибку. Это повышает надёжность кода и исключает неоднозначные интерпретации.
Тип Char предназначен для хранения одного символа Юникода. Он занимает 32 бита и может представлять любой символ из стандарта Unicode, включая эмодзи и специальные математические знаки. Символ записывается в одинарных кавычках: 'A', 'α', '🙂'. Строки в Julia — это последовательности символов, но реализованы как неизменяемые массивы кодовых точек UTF-8, что обеспечивает эффективную работу с многоязычным текстом.
Составные типы: структуры и кортежи
Julia позволяет создавать собственные составные типы с помощью ключевого слова struct. Структура объединяет несколько полей, каждое из которых может иметь свой тип. Например:
struct Point
x::Float64
y::Float64
end
Этот код определяет новый конкретный тип Point с двумя полями. По умолчанию структуры в Julia неизменяемы: после создания экземпляра изменить его поля нельзя. Для создания изменяемой структуры используется ключевое слово mutable struct.
Кортежи (Tuple) — это ещё один вид составного типа, но в отличие от структур, они не имеют именованных полей, а только позиции. Кортежи создаются с помощью круглых скобок: (1, "hello", 3.14). Тип такого кортежа будет Tuple{Int64, String, Float64}. Кортежи неизменяемы и часто используются для возврата нескольких значений из функции или временного группирования данных.
Обобщённое программирование и диспетчеризация
Одной из ключевых особенностей Julia является система множественной диспетчеризации, основанная на типах аргументов функций. Функция в Julia может иметь множество методов, каждый из которых вызывается в зависимости от типов переданных аргументов. Эта система тесно связана с типами данных и позволяет писать код, который автоматически адаптируется к различным входным данным.
Например, можно определить функцию area, которая вычисляет площадь разных фигур:
area(r::Float64) = π * r^2 # для круга
area(w::Float64, h::Float64) = w * h # для прямоугольника
Здесь компилятор выбирает нужный метод на основе количества и типов аргументов. Такой подход поощряет использование конкретных типов и делает код одновременно гибким и быстрым.
Аннотации типов в Julia не обязательны, но их использование помогает документировать код, улучшать читаемость и давать подсказки компилятору для оптимизации. Аннотация не ограничивает переменную одним типом в глобальной области видимости, но внутри функций она может помочь компилятору сгенерировать более эффективный машинный код.